﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Node.cs" company="dimamartovoi">
//   Apache License
//   Version 2.0, January 2004
//   http://www.apache.org/licenses
// </copyright>
// <summary>
//   Defines the Node type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace ActorModel.Engine.Core
{
    public sealed class Node<TState>
    {
        private readonly IEnumerable<Connector<TState>> connectors;

        private readonly AutoResetEvent evoluteWaitHandle = new AutoResetEvent(true);

        private readonly int id;

        private TState state;

        public Node(
            int id, 
            TState state, 
            IEnumerable<Connector<TState>> connectors, 
            Func<TState, IEnumerable<TState>, IEnumerable<TState>> evolutionFunc)
        {
            this.id = id;
            this.state = state;
            this.EvolutionFunc = evolutionFunc;
            this.connectors = connectors;

            this.Initialize();
        }

        public event Action<NodeStateChangedEventArgs<TState>> NodeStateChanged;

        public Func<TState, IEnumerable<TState>, IEnumerable<TState>> EvolutionFunc { get; set; }

        public int Id
        {
            get
            {
                return this.id;
            }
        }

        public TState State
        {
            get
            {
                return this.state;
            }

            private set
            {
                this.state = value;
                this.OnNodeStateChanged(new NodeStateChangedEventArgs<TState>(this, this.state));
            }
        }

        public void Evolute()
        {
            this.evoluteWaitHandle.WaitOne();

            IEnumerable<TState> states = this.connectors.Select(connector => connector.State).ToArray();
            IEnumerable<TState> newStates = this.EvolutionFunc(this.State, states);
            TState[] newStatesArray = newStates as TState[] ?? newStates.ToArray();
            this.State = newStatesArray[0];
            this.NotifyStateChanged(newStatesArray.Skip(1));
        }

        public override string ToString()
        {
            var connectorsStringBuilder = new StringBuilder();
            foreach (var connector in this.connectors)
            {
                connectorsStringBuilder.Append(connector);
            }

            return string.Format(
                "\r\nNode: id = [{0}]; state = [{1}]; \r\nConnectors:\r\n{2}", 
                this.id, 
                this.state, 
                connectorsStringBuilder);
        }

        private void ConnectorOnStateReceived(ConnectorStateEventArgs<TState> connectorStateEventArgs)
        {
            if (this.connectors.All(c => c.HasArrivedState))
            {
                this.evoluteWaitHandle.Set();
            }
        }

        private void Initialize()
        {
            foreach (var connector in this.connectors)
            {
                connector.StateReceived += this.ConnectorOnStateReceived;
            }
        }

        private void NotifyStateChanged(IEnumerable<TState> states)
        {
            TState[] enumerable = states as TState[] ?? states.ToArray();
            for (int i = 0; i < this.connectors.Count(); i++)
            {
                Connector<TState> connector = this.connectors.ElementAt(i);
                connector.Send(enumerable[i]);
            }
        }

        private void OnNodeStateChanged(NodeStateChangedEventArgs<TState> obj)
        {
            Action<NodeStateChangedEventArgs<TState>> handler = this.NodeStateChanged;
            if (handler != null)
            {
                handler(obj);
            }
        }
    }
}